package tps12.axwax.client;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.client.rpc.AsyncCallback;

public interface PuzzleClient {

	void getPuzzleList(final AsyncCallback<Collection<PuzzleDescription>> callback);

	void getPuzzle(String url, final AsyncCallback<Puzzle> callback);
	
	void getPuzzleLetters(String url, final AsyncCallback<String> callback);
	
	void putPuzzleLetters(String url, String letters, final AsyncCallback<Boolean> callback);

	void postPuzzle(String xml, final AsyncCallback<String> callback);
	
	static public class PuzzleDescription {
		public PuzzleDescription(String title, String url) {
			this.title = title;
			this.url = url;
		}
		public final String title;
		public final String url;
	}
	
	static public class Cell {
		public Cell(int n, boolean left, boolean top) {
			number = n;
			leftBar = left;
			topBar = top;
		}
		
		public Cell(JSONObject c) throws BadPuzzleDataException {
			try {
				number = (int)c.get("number").isNumber().doubleValue();
				leftBar = c.get("left").isBoolean().booleanValue();
				topBar = c.get("top").isBoolean().booleanValue();
			}
			catch (NullPointerException e) {
				throw new BadPuzzleDataException("Incorrect cell data", e);
			}
		}
		
		public int number;
		public boolean leftBar;
		public boolean topBar;
	}
	
	static public class BarredRectangularGrid {
		public BarredRectangularGrid(Cell[][] cells) {
			width = -1;
			height = cells.length;
			rows = new ArrayList<List<Cell>>(height);
			for (int i = 0; i < height; i++) {
				if (width == -1) width = cells[i].length;
				List<Cell> row = new ArrayList<Cell>(width);
				for (int j = 0; j < width; j++)
					row.add(cells[i][j]);
				rows.add(row);
			}
		}
		
		public BarredRectangularGrid(JSONArray rs) throws BadPuzzleDataException {
			width = -1;
			try {
				height = rs.size();
				rows = new ArrayList<List<Cell>>(height);
				for (int i = 0; i < height; i++) {
					
					JSONArray r = rs.get(i).isArray();
					
					if (width == -1) width = r.size();
					else if (r.size() != width)
						throw new Exception("Jagged grid not permitted");
					
					List<Cell> row = new ArrayList<Cell>(width);
					for (int j = 0; j < width; j++)
						row.add(new Cell(r.get(j).isObject()));
					rows.add(row);
				}
			}
			catch (BadPuzzleDataException e) {
				throw e;
			}
			catch (Throwable t) {
				throw new BadPuzzleDataException("Incorrect grid data", t);
			}
		}
		
		public int width;
		public int height;
		
		public List<List<Cell>> rows;
	}
	
	static public class Clue {
		public Clue(int num, String clue, String fmt) {
			number = num;
			text = clue;
			format = fmt;
		}
		
		public Clue(JSONObject c) throws BadPuzzleDataException {
			try {
				number = (int)c.get("number").isNumber().doubleValue();
				text = c.get("text").isString().stringValue();
				format = c.get("format").isString().stringValue();
			}
			catch (NullPointerException e) {
				throw new BadPuzzleDataException("Incorrect clue data", e);
			}
		}

		public int number;
		public String text;
		public String format;
	}
	
	static public class ClueList extends ArrayList<Clue> {
		public ClueList(Collection<Clue> clues) {
			super(clues.size());
			addAll(clues);
		}
		
		public ClueList(JSONArray a) throws BadPuzzleDataException {	
			super(a.size());
			try {
				for (int i = 0; i < a.size(); i++)
					add(new Clue(a.get(i).isObject()));
			}
			catch (BadPuzzleDataException e) {
				throw e;
			}
			catch (Throwable t) {
				throw new BadPuzzleDataException("Incorrect clue list data", t);
			}
		}
		
		private static final long serialVersionUID = 37814137311202928L;
	}
	
	static public class Puzzle {
		public Puzzle(String title, String instructions,
				BarredRectangularGrid grid,
				Collection<Clue> across, Collection<Clue> down,
				String lettersUrl) {
			this.title = title;
			this.instructions = instructions;
			this.grid = grid;
			this.across = across;
			this.down = down;
			this.lettersUrl = lettersUrl;
		}
		
		public Puzzle(JSONObject p) throws BadPuzzleDataException {
			title = p.get("title").isString().stringValue();
			instructions = p.get("instructions").isString().stringValue();
			grid = new BarredRectangularGrid(p.get("grid").isArray());
			try {
				across = new ClueList(p.get("across").isArray());
				down = new ClueList(p.get("down").isArray());
			}
			catch (BadPuzzleDataException e) {
				throw e;
			}
			catch (Throwable t) {
				throw new BadPuzzleDataException("Bad puzzle data", t);
			}
			lettersUrl = p.get("letters").isString().stringValue();
		}
		
		public String title;
		public String instructions;
		public BarredRectangularGrid grid;
		public Collection<Clue> across;
		public Collection<Clue> down;
		public String lettersUrl;
	}
	
	static public class BadPuzzleDataException extends Exception {
		public BadPuzzleDataException(String message, Throwable cause) {
			super(message, cause);
		}

		private static final long serialVersionUID = 2128220329027573785L;
	}
}